DOM

主要部分: DOM 事件的级别,DOM 事件模型,DOM 事件流,DOM 事件捕获的具体流程,Event 对象的常见应用,自定义事件 这几个部分的内容。

什么是 window 对象? 什么是 document 对象

window

  • Window 对象表示当前浏览器的窗口,是 JavaScript 的顶级对象。
  • 我们创建的所有对象、函数、变量都是 Window 对象的成员。
  • Window 对象的方法和属性是在全局范围内有效的。

document

  • Document 对象是 HTML 文档的根节点与所有其他节点(元素节点,文本节点,属性节点, 注释节点)
  • Document 对象使我们可以通过脚本对 HTML 页面中的所有元素进行访问
  • Document 对象是 Window 对象的一部分,即 window.document

获得一个 DOM 元素的绝对位置

  • offsetTop:返回当前元素相对于其 offsetParent 元素的顶部的距离
  • offsetLeft:返回当前元素相对于其 offsetParent 元素的左边的距离
  • getBoundingClientRect():返回值是一个 DOMRect 对象,它包含了一组用于描述边框的只读属性——left、top、right 和 bottom,属性单位为像素

offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别

  • offsetWidth/offsetHeight 返回值包含 content + padding + border,效果与 e.getBoundingClientRect()相同

  • clientWidth/clientHeight 返回值只包含 content + padding,如果有滚动条,也不包含滚动条

  • scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸

  • body.offsetHeight :body 总高度。

  • body.offsetWidth :body 总宽度。

  • body.clientHeight :body 展示的高度;表示 body 在浏览器内显示的区域高度。

  • body.clientWidth :body 展示的宽度;表示 body 在浏览器内显示的区域宽度。

滚动条高度/宽度 :如高度,可通过浏览器内页面可用高度 - body 展示高度得出,即 window.innerHeight - body.clientHeight。

BOM

BOM 是 browser object model 的缩写, 简称浏览器对象模型。 主要处理浏览器窗口和框架,描述了与浏览器进行交互的方法和接口, 可以对浏览器窗口进行访问和操作, 譬如可以弹出新的窗口, 回退历史记录, 获取 url。

BOM 对象包含哪些内容?

  • Window JavaScript 层级中的顶层对象, 表示浏览器窗口。
  • Navigator 包含客户端浏览器的信息。
  • History 包含了浏览器窗口访问过的 URL。
  • Location 包含了当前 URL 的信息。
  • Screen 包含客户端显示屏的信息。

BOM 与 DOM 的关系

  • js 是通过访问 BOM 对象来访问、 控制、 修改浏览器
  • BOM 的 window 包含了 document, 因此通过 window 对象的 document 属性就可以访问、检索、 修改文档内容与结构。
  • document 对象又是 DOM 模型的根节点。

因此, BOM 包含了 DOM, 浏览器提供出来给予访问的是 BOM 对象, 从 BOM 对象再访问到 DOM 对象, 从而 js 可以操作浏览器以及浏览器读取到的文档

History 对象

History 对象包含用户(在浏览器窗口中) 访问过的 URL

方法/属性描述
length返回浏览器历史列表中的 URL 数量。
back()加载 history 列表中的前一个 URL。
forward()加载 history 列表中的下一个 URL。
go()加载 history 列表中的某个具体页面

location 对象

location 对象包含有关当前 URL 的信息。

属性描述
hash设置或返回从井号 (#) 开始的 URL(锚) 。
host设置或返回主机名和当前 URL 的端口号。
hostname设置或返回当前 URL 的主机名。
href设置或返回完整的 URL。
pathname设置或返回当前 URL 的路径部分。
port设置或返回当前 URL 的端口号。
protocol设置或返回当前 URL 的协议。
search置或返回从问号 (?) 开始的 URL(查询部分) 。

返回由客户机发送服务器的 user-agent 头部的值。

事件

DOM 事件级别

  1. DOM0: onXXX 类型的定义事件
  2. DOM2: addEventListener, removeEventListener
  3. DOM3: 增加了很多事件类型

DOM 事件模型

捕获从上到下, 冒泡从下到上。先捕获,再到目标,再冒泡

DOM 事件捕获的具体流程: 从 window -> document -> html -> body -> ... -> 目标元素

事件模型

W3C 中定义事件的发生经历三个阶段:捕获阶段(capturing)、目标阶段(targeting)、冒泡阶段(bubbling)

冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发 捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发

阻止冒泡:在 W3c 中,使用 stopPropagation()方法;在 IE 下设置 cancelBubble = true 阻止捕获:阻止事件的默认行为,例如 click 后的跳转。在 W3c 中,使用 preventDefault()方法,在 IE 下设置 window.event.returnValue = false

DOM 事件流

事件流分为两种: 捕获事件流和冒泡事件流。

捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。

DOM 事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段

阻止冒泡事件 event.stopPropagation()

function stopBubble(e) {
  if (e && e.stopPropagation) {
    // 如果提供了事件对象 event 这说明不是 IE 浏览器
    e.stopPropagation();
  } else {
    window.event.cancelBubble = true; // IE 方式阻止冒泡
  }
}

阻止默认行为 event.preventDefault()

function stopDefault(e) {
  if (e && e.preventDefault) {
    e.preventDefault();
  } else {
    // IE浏览器阻止函数器默认动作的行为
    window.event.returnValue = false;
  }
}

事件触发三阶段

事件流动顺序 ?

  • document 往事件触发处传播,遇到注册的捕获事件会触发
  • 传播到事件触发处时触发注册的事件
  • 从事件触发处往 document 传播,遇到注册的冒泡事件会触发

事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行

// 以下会先打印冒泡然后是捕获
node.addEventListener(
  "click",
  (event) => {
    console.log("冒泡");
  },
  false
);
node.addEventListener(
  "click",
  (event) => {
    console.log("捕获 ");
  },
  true
);

在一个 DOM 上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?

  • 该 DOM 上的事件如果被触发,会执行两次(执行次数等于绑定次数)
  • 如果该 DOM 是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
  • 如果该 DOM 是处于事件流中的非目标元素,则先执行捕获,后执行冒泡

addEventListener 有哪些参数?

有三个参数,第一个是事件的类型,第二个是事件的回调函数,第三个是一个表示事件是冒泡阶段还是捕获阶段捕获的布尔值,true 表示捕获,false 表示冒泡

Event 对象常见应用

  • event.target 触发事件的元素
  • event.currentTarget 绑定事件的元素
  • event.preventDefault() 阻止默认行为
  • event.stopPropagation() 阻止在捕获阶段或冒泡阶段继续传播,而不是阻止冒泡
  • event.stopImmediatePropagation() 阻止事件冒泡并且阻止相同事件的其他侦听器被调用。

如何派发事件(dispatchEvent)?(如何进行事件广播?)

  • W3C: 使用 dispatchEvent 方法
  • IE: 使用 fireEvent 方法
var fireEvent = function (element, event) {
  if (document.createEventObject) {
    var mockEvent = document.createEventObject();
    return element.fireEvent("on" + event, mockEvent);
  } else {
    var mockEvent = document.createEvent("HTMLEvents");
    mockEvent.initEvent(event, true, true);
    return !element.dispatchEvent(mockEvent);
  }
};

自定义事件

  • Event
  • CustomEvent
var btn = document.querySelector("#btn");
/*
 * 第一个参数是事件类型
 * 第二个参数是一个对象
 */
var ev = new CustomEvent("alert", {
  bubbles: "true",
  cancelable: "true",
  detail: "button",
});
btn.addEventListener(
  "alert",
  function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable); //true
    console.log(event.detail); //button
  },
  false
);
btn.dispatchEvent(ev);

注册事件

  • 通常我们使用 addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 falseuseCapture 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性:

    • capture:布尔值,和 useCapture 作用一样
    • once:布尔值,值为 true 表示该回调只会调用一次,调用后会移除监听
    • passive:布尔值,表示永远不会调用 preventDefault
  • 一般来说,我们只希望事件只触发在目标上,这时候可以使用 stopPropagation 来阻止事件的进一步传播。通常我们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件

node.addEventListener(
  "click",
  (event) => {
    event.stopImmediatePropagation();
    console.log("冒泡");
  },
  false
);
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener(
  "click",
  (event) => {
    console.log("捕获 ");
  },
  true
);

事件代理

事件代理又称之为事件委托。将事件绑定目标元素的到父元素上,利用冒泡机制触发该事件。

优点:

  • 可以减少事件注册,节省大量内存占用
  • 可以将事件应用于动态添加的子元素上
  • 提高运行效率(尤其对于需要循环操作的列表)
  • 动态添加后的元素,仍然可以有这些事件(对比与给自己添加的事件,后续动态添加时,新来的这些元素不会有这些事件)

但使用不当会造成事件在不应该触发时触发

ulEl.addEventListener(
  "click",
  function (e) {
    var target = event.target || event.srcElement;
    if (target && target.nodeName.toUpperCase() === "LI") {
      console.log(target.innerHTML);
    }
  },
  false
);

target 与 currentTarget 的区别?

target 是触发事件的某个具体的对象,是直接接受事件的目标 DOM 元素,即 "谁触发了事件,谁就是 target "。currentTarget 是绑定事件的对象。

  • target 只会出现在事件流的目标阶段,currentTarget 可能出现在事件流的任何阶段
  • 当事件流处在目标阶段时,二者的指向相同
  • 当事件流处于捕获或冒泡阶段时:currentTarget 指向当前事件活动的对象(一般为父级)

事件、IE 与火狐的事件机制有什么区别? 如何阻止冒泡?

  1. 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。
  2. 事件处理机制:IE 是事件冒泡、firefox 同时支持两种事件模型,也就是:捕获型事件和冒泡型事件。;
  3. ev.stopPropagation();注意旧 ie 的方法 ev.cancelBubble = true;

document 中的load事件和DOMContentLoaded事件之间的区别是什么?

初始的 HTML 文档被完全加载和解析完成之后(DOM 树构建完成),DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架等其他资源的完成加载。

windowload事件仅在 DOM 和所有相关资源全部完成加载后(页面加载完毕)才会触发。

onready 比 onload 先执行

执行时间 window.onload 必须等到页面内包括图片的所有元素加载完毕后才能执行。 $(document).ready()是 DOM 结构绘制完毕后就执行,不必等到加载完毕。

onload 只执行最后一个而 onready 可以执行多个。

编写个数不同 window.onload 不能同时编写多个,如果有多个 window.onload 方法,只会执行一个。 $(document).ready()可以同时编写多个,并且都可以得到执行 $(document).ready(function(){})可以简写成$(function(){});

写一个通用的事件侦听器函数

my.Event = {
  // 页面加载完成后
  readyEvent: function (fn) {
    if (fn == null) {
      fn = document;
    }
    var oldonload = window.onload;
    if (typeof window.onload != "function") {
      window.onload = fn;
    } else {
      window.onload = function () {
        oldonload();
        fn();
      };
    }
  },
  // 视能力分别使用dom0||dom2||IE方式 来绑定事件
  // 参数: 操作的元素,事件名称 ,事件处理程序
  addEvent: function (element, type, handler) {
    if (element.addEventListener) {
      //事件类型、需要执行的函数、是否捕捉
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent("on" + type, function () {
        handler.call(element);
      });
    } else {
      element["on" + type] = handler;
    }
  },
  // 移除事件
  removeEvent: function (element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.datachEvent) {
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  },
  // 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
  stopPropagation: function (ev) {
    if (ev.stopPropagation) {
      ev.stopPropagation();
    } else {
      ev.cancelBubble = true;
    }
  },
  // 取消事件的默认行为
  preventDefault: function (event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  },
  // 获取事件目标
  getTarget: function (event) {
    return event.target || event.srcElement;
  },
  // 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
  getEvent: function (e) {
    var ev = e || window.event;
    if (!ev) {
      var c = this.getEvent.caller;
      while (c) {
        ev = c.arguments[0];
        if (ev && Event == ev.constructor) {
          break;
        }
        c = c.caller;
      }
    }
    return ev;
  },
};

requestIdleCallback 和 requestAnimationFrame 区别

requestIdleCallback

  • requestIdleCallback 是一个兼容性不那么好的功能,所以我们使用前得判断它是否支持
  • 我们可以使用 window.requestIdleCallback() 方法来插入一个函数,这个函数将在浏览器空闲时被调用;requestIdleCallback
  • 它的参数为 callback 和 可选的 timeout;如果指定了 timeout 且为正值;则回调在 timeout 毫秒后还没调用时,回调任务就会被放入事件循环里排队,这样做可能会影响性能;

注意点:因为它发生在一帧的最后,此时页面布局已经完成,所以不建议在 requestIdleCallback 里再操作 DOM,这样会导致页面再次重绘。

requestAnimationFrame

  • window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
  • 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

说说 requestAnimationFrame 的作用,并实现获取每秒的帧数

Last Updated:
Contributors: yiliang114